/////////////////////////////////////////////////////////////////////////////
// Purpose:     Timers management
// Author:      Jesus Gonzalez
// Modified by:
// Copyright:   (c) 2003-2013 Jesus Gonzalez <jgonzalez@gdr-sistemas.com>
// License:     BSD License
/////////////////////////////////////////////////////////////////////////////

#include "SmartLib/Timer.h"

#include <signal.h>
#include <time.h>
#include <errno.h>
#include <string.h>

#include "Log.h"

#define MS_TO_NS(x) (x * 1000 * 1000)

#define INVALID_TIMER ((timer_t)-1)

/**
 * Private class to store system specific data.
 */
class TimerPrvSys
{
public:
    TimerPrvSys() : timerId(INVALID_TIMER) {};
    timer_t timerId;
};

bool tsh_initialized = false;

static void timer_signal_handler(int sig, siginfo_t *si, void *uc)
{
    Timer::_SignalHandler( (Timer*) si->si_value.sival_ptr );
}

static void timer_thread_entry(sigval_t sv)
{
    Timer::_SignalHandler( (Timer*) sv.sival_ptr );
}

Timer::Timer()
: m_prvsys(new TimerPrvSys), m_handler(NULL), m_handlerPtr(NULL), m_invokeThread(false), m_timerId(-1)
{
}

Timer::~Timer()
{
    Stop();
    delete m_prvsys;
}

void Timer::_SignalHandler(Timer* timer)
{
    timer->m_handler->OnTimer( *timer );
}

void Timer::setHandler(ITimerHandler* handler, bool invokeThread, int timerId)
{
    Stop();

    m_handler = handler;
    m_handlerPtr = NULL;
    m_invokeThread = invokeThread;
    m_timerId = timerId;
}

void Timer::setHandler(const TimerHandler::Ptr &handler, bool invokeThread, int timerId)
{
    setHandler(~handler, timerId);
    m_invokeThread = invokeThread;
    m_handlerPtr = handler;
}

void Timer::Start(uint period_ms, bool one_shot)
{
    if( m_prvsys->timerId == INVALID_TIMER )
    {
        Init();
    }

    if( (m_handler == NULL) && !one_shot )
    {
        THROW_ERROR("If no timer handler is specified, timer can only be 'one-shot'");
    }

    struct itimerspec ts;
    ts.it_value.tv_sec = period_ms/1000;
    ts.it_value.tv_nsec = MS_TO_NS(period_ms%1000);
    if( one_shot )
    {
        ts.it_interval.tv_sec = 0;
        ts.it_interval.tv_nsec = 0;
    }
    else
    {
        ts.it_interval.tv_sec = ts.it_value.tv_sec;
        ts.it_interval.tv_nsec = ts.it_value.tv_nsec;
    }

    if( timer_settime(m_prvsys->timerId, 0, &ts, NULL) < 0 )
    {
        THROW_ERROR("Couldn't arm timer (Error = %s)", strerror(errno));
    }

    LOG_DEBUG("Timer started");
}

void Timer::Stop()
{
    if( m_prvsys->timerId != INVALID_TIMER )
    {
        timer_delete(m_prvsys->timerId);
        m_prvsys->timerId = INVALID_TIMER;
    }

    LOG_DEBUG("Timer stopped");
}

bool Timer::isExpired() const
{
    if( m_prvsys->timerId == INVALID_TIMER )
    {
        return true;
    }

    struct itimerspec ts;
    if( timer_gettime(m_prvsys->timerId, &ts) < 0 )
    {
        THROW_ERROR("Couldn't get timer value (Error = %s)", strerror(errno));
    }
    return (ts.it_value.tv_sec < 0);
}

void Timer::Init()
{
    struct sigevent sevp;
    memset (&sevp, 0, sizeof (struct sigevent));
    if( m_handler == NULL )
    {
        sevp.sigev_notify = SIGEV_NONE;
    }
    else if( m_invokeThread )
    {
        sevp.sigev_notify = SIGEV_THREAD;
        sevp.sigev_notify_function = timer_thread_entry;
        sevp.sigev_notify_attributes = NULL;
        sevp.sigev_value.sival_ptr = this;
    }
    else
    {
        // Set global signal handler if needed
        if( tsh_initialized == false )
        {
            struct sigaction sa;
            sa.sa_flags = SA_SIGINFO;
            sa.sa_sigaction = timer_signal_handler;
            sigemptyset(&sa.sa_mask);
            sa.sa_flags = SA_SIGINFO;
            if( sigaction(SIGRTMIN, &sa, NULL) < 0 )
            {
                THROW_ERROR("Couldn't set signal handler (Error = %s)", strerror(errno));
            }
            tsh_initialized = true;
        }

        sevp.sigev_notify = SIGEV_SIGNAL;
        sevp.sigev_signo = SIGRTMIN;
        sevp.sigev_value.sival_ptr = this;
    }

    if( timer_create(CLOCK_REALTIME, &sevp, &m_prvsys->timerId) < 0 )
    {
        THROW_ERROR("Couldn't create timer (Error = %s)", strerror(errno));
    }

    LOG_DEBUG("Timer created");
}

void Timer::Pause(uint time_ms)
{
    struct timespec ts;
    ts.tv_sec = time_ms/1000;
    ts.tv_nsec = MS_TO_NS(time_ms%1000);

    while(true)
    {
        if( nanosleep(&ts, &ts) < 0 )
        {
            if( errno == EINTR ) continue;

            THROW_ERROR("Couldn't suspend the thread (Error = %s)", strerror(errno));
        }
        break;
    }
}

TimerEx::TimerEx(bool invokeThread)
{
    setHandler(this, invokeThread);
}
